package command

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
	agentv1beta1 "gopkg.openfuyao.cn/bkeagent/api/v1beta1"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"

	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
)

type ENV struct {
	BaseCommand

	Nodes bkenode.Nodes

	BkeConfigName string

	Extra []string

	ExtraHosts []string

	DryRun bool

	PrePullImage bool

	DeepRestore bool
}

func (e *ENV) Validate() error {
	if e.Client == nil {
		return errors.New("client is nil")
	}
	if lenNodes := e.Nodes.Length(); lenNodes == 0 {
		return errors.New("env command except at least one node but got " + strconv.Itoa(lenNodes))
	}
	if e.BkeConfigName == "" {
		return errors.New("bkeConfigName is empty")
	}
	return e.BaseCommand.validate()
}

func (e *ENV) NewConatinerdReset() error {
	if err := e.Validate(); err != nil {
		return err
	}
	commandName := K8sContainerdResetCommandName
	bkeConfigStr := fmt.Sprintf("bkeConfig=%s:%s", e.NameSpace, e.BkeConfigName)
	extra := fmt.Sprintf("extra=%s", strings.Join(e.Extra, ","))

	commandName = fmt.Sprintf("%s-%d", commandName, time.Now().Unix())
	commandSpec := GenerateDefaultCommandSpec()
	commandSpec.TTLSecondsAfterFinished = 0
	commandSpec.Commands = []agentv1beta1.ExecCommand{
		{
			ID: "reset",
			Command: []string{
				"Reset",
				bkeConfigStr,
				"scope=containerd-cfg",
				extra,
			},
			Type:          agentv1beta1.CommandBuiltIn,
			BackoffIgnore: true,
		},
	}
	commandSpec.NodeSelector = getNodeSelector(e.Nodes)

	return e.newCommand(commandName, BKEClusterLabel, commandSpec)
}

func (e *ENV) NewConatinerdRedeploy() error {
	if err := e.Validate(); err != nil {
		return err
	}
	commandName := K8sContainerdRedeployCommandName
	bkeConfigStr := fmt.Sprintf("bkeConfig=%s:%s", e.NameSpace, e.BkeConfigName)

	commandName = fmt.Sprintf("%s-%d", commandName, time.Now().Unix())
	commandSpec := GenerateDefaultCommandSpec()
	commandSpec.TTLSecondsAfterFinished = 0
	commandSpec.Commands = []agentv1beta1.ExecCommand{
		{
			ID: "init and check node env",
			Command: []string{
				"K8sEnvInit",
				"init=true",
				"check=true",
				"scope=runtime",
				bkeConfigStr,
			},
			Type:          agentv1beta1.CommandBuiltIn,
			BackoffDelay:  5,
			BackoffIgnore: false,
		},
	}
	commandSpec.NodeSelector = getNodeSelector(e.Nodes)

	return e.newCommand(commandName, BKEClusterLabel, commandSpec)
}

func (e *ENV) New() error {
	if err := e.Validate(); err != nil {
		return err
	}
	commandName := K8sEnvCommandName
	if e.DryRun {
		commandName = K8sEnvDryRunCommandName
	}
	// aright start create new env command
	bkeConfigStr := fmt.Sprintf("bkeConfig=%s:%s", e.NameSpace, e.BkeConfigName)
	extra := fmt.Sprintf("extra=%s", strings.Join(e.Extra, ","))
	extraHosts := fmt.Sprintf("extraHosts=%s", strings.Join(e.ExtraHosts, ","))

	var scope string
	if e.DeepRestore {
		scope = fmt.Sprintf("scope=cert,manifests,container,kubelet,containerRuntime,extra")
	} else {
		scope = fmt.Sprintf("scope=cert,manifests,container,kubelet,extra")
	}

	commandName = fmt.Sprintf("%s-%d", commandName, time.Now().Unix())
	commandSpec := GenerateDefaultCommandSpec()
	commandSpec.TTLSecondsAfterFinished = 0
	commandSpec.Commands = []agentv1beta1.ExecCommand{

		// Check whether the host hardware resources are sufficient to run k8s
		{
			ID: "node hardware resources check",
			Command: []string{
				"K8sEnvInit",
				"init=true",
				"check=true",
				"scope=node",
				bkeConfigStr,
			},
			Type:          agentv1beta1.CommandBuiltIn,
			BackoffIgnore: false,
		},
		// reset node
		{
			ID: "reset",
			Command: []string{
				"Reset",
				bkeConfigStr,
				scope,
				extra,
			},
			Type:          agentv1beta1.CommandBuiltIn,
			BackoffIgnore: true,
		},

		// init node env to run k8s
		{
			ID: "init and check node env",
			Command: []string{
				"K8sEnvInit",
				"init=true",
				"check=true",
				"scope=time,hosts,dns,kernel,firewall,selinux,swap,httpRepo,runtime,iptables,registry,extra",
				bkeConfigStr,
				extraHosts,
			},
			Type:          agentv1beta1.CommandBuiltIn,
			BackoffDelay:  5,
			BackoffIgnore: false,
		},
	}

	if e.DryRun {
		commandSpec.Commands = commandSpec.Commands[:1]
	}
	if e.PrePullImage {
		// only send this command in first deploy
		// Failure to execute this command will not affect the deployment of the entire cluster
		prePullImagesCommandName := fmt.Sprintf("%s-%d", K8sImagePrePullCommandName, time.Now().Unix())
		prePullImagesCommandSpec := GenerateDefaultCommandSpec()
		prePullImagesCommandSpec.Commands = []agentv1beta1.ExecCommand{
			// pre pull images command
			{
				ID: "pre pull images",
				Command: []string{
					"K8sEnvInit",
					"init=true",
					"check=true",
					"scope=image",
					bkeConfigStr,
				},
				Type:          agentv1beta1.CommandBuiltIn,
				BackoffDelay:  15,
				BackoffIgnore: true,
			},
		}
		selector := getNodeSelector(e.Nodes)
		// exclude master node which will be init
		delete(selector.MatchLabels, e.Nodes.Master()[0].IP)
		prePullImagesCommandSpec.NodeSelector = selector

		// ignore error
		_ = e.newCommand(prePullImagesCommandName, BKEClusterLabel, prePullImagesCommandSpec)
	}

	commandSpec.NodeSelector = getNodeSelector(e.Nodes)

	return e.newCommand(commandName, BKEClusterLabel, commandSpec)
}

func (e *ENV) Wait() (error, []string, []string) {
	err, complete, successNodes, failedNodes := e.waitCommandComplete()
	// means all command not executed
	if !complete && len(failedNodes) == 0 {
		for _, node := range e.Nodes {
			if utils.ContainsString(successNodes, node.Hostname) {
				continue
			}
			failedNodes = append(failedNodes, node.Hostname)
		}
	}
	return err, successNodes, failedNodes
}
